/**
 * Created by Weizehua on 2017/1/13.
 */

import "reflect-metadata"
import {assert} from "chai"
import {suite, test} from "mocha-typescript"
import {Injectable, Injector, Singleton, Inject} from "../src/Injector";

import {SingletonA} from "./singleton_a"
import {SingletonB} from "./singleton_b"
import {LoopDependencySingleton} from "./loop_dependency_singleton";
import {LoopDependencyA} from "./loop_dependency_a";
import {FactorOf} from "../src/Injector";


@Injectable()
class A {
}

@Injectable()
class B {
    val = '1234';

    constructor(private a: A) {
    }
}

@Singleton()
class SingletonClass {
    val = '1234';

    constructor(private a: A) {
    }
}

@Singleton()
class SingletonClassForSet {
}

@Injectable()
class InjectPropertyClass {
    val = '1234';
    @Inject()
    b: B;
}

@Injectable()
class TypedInjectClass {
    val = '1234';
    @Inject(() => B)
    b: B;
}

@Injectable()
class FA {
    val = '1234';
}

class FactorFA{
    @FactorOf(FA)
    static factorOfA():FA{
        return {val: 'factor!'}
    }
}

@suite
class InjectorTest {
    @test
    'get @Injectable()'() {
        let b = Injector.get(B);
        assert.equal(b.val, '1234');
    }

    @test
    'get @Singleton()'() {
        let b = Injector.get(SingletonClass);
        assert.equal(b.val, '1234');
    }

    @test
    'get @Inject()'() {
        let b = Injector.get(InjectPropertyClass);
        assert.equal(b.val, '1234');
    }

    @test
    'get @Inject(()=>Type)'() {
        let b = Injector.get(TypedInjectClass);
        assert.equal(b.val, '1234');
    }

    @test
    'Injector.setSingleton()'() {
        let s1 = new SingletonClassForSet();
        Injector.setSingleton(SingletonClassForSet, s1);
        let s2 = Injector.get(SingletonClassForSet);
        assert.equal(s1, s2);
    }

    @test
    '@FactorOf(Class)' (){
        let normal1 = new FA();
        let normal2 = new FA();

        let factor1 = Injector.get(FA);
        let factor2 = Injector.get(FA);

        assert.equal(normal1.val, normal2.val);
        assert.equal(normal1.val, '1234');

        assert.notEqual(normal1.val, factor1.val);

        assert.equal(factor1.val, factor2.val);
        assert.equal(factor1.val, 'factor!');

    }

    @test
    'different @Injectable() instance should reference to different object'() {
        let b = Injector.get(B);
        let b2 = Injector.get(B);
        let newVal = 'zzzz';
        b.val = newVal;
        assert.equal(b.val, newVal);
        assert.equal(b2.val, '1234');
        assert.notEqual(b.val, b2.val);
    }

    @test
    'different @Singleton() instance should reference to the same object'() {
        let b = Injector.get(SingletonClass);
        let b2 = Injector.get(SingletonClass);
        let newVal = 'zzzz';
        b.val = newVal;
        assert.equal(b.val, newVal);
        assert.equal(b2.val, newVal);
    }
    @test
    'A->B->A dependency is available() when there is a @Singleton among them'() {
        let s = Injector.get(LoopDependencySingleton);
        let a = Injector.get(LoopDependencyA);

        // first: access property then instantiate object
        assert.equal(s, a.s, '@Singleton should equal');
        assert.notEqual(a, s.a, '@Injectable() should not equal');
        assert.equal(s, s.a.s);

        // second: get existing property
        assert.equal(s, a.s);
        assert.notEqual(a, s.a);
        assert.equal(s, s.a.s);
    }

    @test
    'A->B->A dependency is available() when there is a @Singleton among them.2'() {
        let sa = Injector.get(SingletonA);
        let sb = Injector.get(SingletonB);

        // first: access property then instantiate object
        assert.equal(sa, sb.sa);
        assert.equal(sb, sa.sb);

        // second: get existing property
        assert.equal(sa, sb.sa);
        assert.equal(sb, sa.sb);
    }

}
